
package com.turing.draw.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.turing.common.entity.ParseTableVO;
import com.turing.common.util.*;
import com.turing.draw.enums.FileStatusEnum;
import com.turing.draw.model.domain.FileTask;
import com.turing.draw.model.domain.OcrPageTask;
import com.turing.draw.dao.OcrPageTaskMapper;
import com.turing.draw.enums.OcrPageTaskStatusEnum;
import com.turing.draw.service.FileTaskService;
import com.turing.draw.service.OcrPageTaskService;
import com.turing.draw.utils.TableUtil;
import com.turing.draw.vo.in.OcrJsonBO;
import com.turing.draw.vo.in.OcrResultBO;
import com.turing.draw.vo.in.OcrTableBO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;


/**
 * @Author livin
 * @Date 2021-11-04
 */
@Service
public class OcrPageTaskServiceImpl extends ServiceImpl<OcrPageTaskMapper, OcrPageTask> implements OcrPageTaskService {

    private static final Logger logger = LoggerFactory.getLogger(OcrPageTaskServiceImpl.class);

    /**
     * 场景ID - 关键字
     * 识别’开基确认书‘场景关键字
     */
    private static final HashMap<String, List<String>> KJ_TEMPLATE_MAPS = new HashMap<>();
    static {
        // nbtg开基申购
        KJ_TEMPLATE_MAPS.put("NB2_3E68FF7C", new ArrayList<>(Arrays.asList("申购", "认购", "中购", "购确认")));
        // nbtg开基赎回
        KJ_TEMPLATE_MAPS.put("NB2_C0AA242E", new ArrayList<>(Arrays.asList("赎回", "续回", "赋回", "回确认")));
        // nbtg开基转换
        KJ_TEMPLATE_MAPS.put("NB2_2405CC0D", new ArrayList<>(Arrays.asList("转换", "转入", "转出")));
        // 分红
        KJ_TEMPLATE_MAPS.put("FH", new ArrayList<>(Arrays.asList("分红", "红利", "发放", "转结")));
        // 强增（减）
        KJ_TEMPLATE_MAPS.put("NB2_983D871C", new ArrayList<>(Arrays.asList("强增", "强减", "强制", "调增", "调减")));
    }

    /**
     * 场景ID - 关键字
     * 识别’TA申购‘场景关键字
     */
    private static final HashMap<String, List<String>> TA_SG_TEMPLATE_MAPS = new HashMap<>();
    static {
        // nbtgTA申购
        TA_SG_TEMPLATE_MAPS.put("NB2_66197293", new ArrayList<>(Arrays.asList("申购", "认购", "到账金额", "募集金额")));
        // nbtgTA赎回
        TA_SG_TEMPLATE_MAPS.put("NB2_D6A96B09", new ArrayList<>(Arrays.asList("赎回", "续回", "赋回", "回确认")));
        // nbtgTA分红
        TA_SG_TEMPLATE_MAPS.put("NB2_6DB87AD7", new ArrayList<>(Arrays.asList("分红", "红利", "发放", "权益登记", "除权")));
    }

    /**
     * 开基确认书场景ID
     */
    private static final String MERGE_TEMPLATE_ID = "NB2_OCR_BIAOZHUN";

    /**
     * 非标合并场景ID
     */
    private static final String MERGE_NON_STANDARD_TEMPLATE_ID = "NB2_OCR_FEIBIAO";

    private static final String TABLE_KEYWORDS = "table";

    @Value("${fdfs.domain}")
    private String FDFS_DOMAIN;

    @Autowired
    private FileTaskService fileTaskService;

    @Resource
    private OcrPageTaskMapper ocrPageTaskMapper;

    @Override
    public Integer getTaskStatusById(String ocrPageTaskSn) {
        OcrPageTask ocrPageTask = lambdaQuery().eq(OcrPageTask::getOcrPageTaskSn, ocrPageTaskSn)
                .eq(OcrPageTask::getIsDel,0)
                .select(OcrPageTask::getOcrPageTaskStatus)
                .one();
        return Optional.ofNullable(ocrPageTask).map(OcrPageTask::getOcrPageTaskStatus).orElse(OcrPageTaskStatusEnum.NOTEXIST.getCode());
    }

    @Override
    public OcrPageTask getOcrPageTask() {
        return lambdaQuery()
            .in(OcrPageTask::getOcrPageTaskStatus, OcrPageTaskStatusEnum.QUEUING.getCode())
            .orderByDesc(OcrPageTask::getTemplateGroup)
            .orderByDesc(OcrPageTask::getTaskSn)
            .orderByDesc(OcrPageTask::getCreatedTime)
            .last("limit 1").one();
    }

    @Override
    public OcrPageTask getTaskById(String ocrPageTaskSn) {
        return lambdaQuery()
                .eq( OcrPageTask::getOcrPageTaskSn, ocrPageTaskSn )
                .eq( OcrPageTask::getIsDel ,0 )
                .select( OcrPageTask::getOcrPageTaskStatus )
                .last("limit 1").one();
    }

    @Override
    public boolean lockOcrPageTask(String ocrPageTaskSn,String domain) {
        return lambdaUpdate()
                .eq(OcrPageTask::getOcrPageTaskSn, ocrPageTaskSn)
                .set(OcrPageTask::getOcrPageTaskStatus, OcrPageTaskStatusEnum.OCRTASKPROCESSING.getCode())
                .set(OcrPageTask::getWarningTime, LocalDateTime.now().plusSeconds(60))
                .set(OcrPageTask::getDomain, domain)
                .update();
    }

    @Override
    public boolean saveJSONResult(String JSONUrl, String ocrPageTaskSn) {
        return lambdaUpdate().eq(OcrPageTask::getOcrPageTaskSn, ocrPageTaskSn)
                .set(OcrPageTask::getOcrJsonUrl, JSONUrl)
                .set(OcrPageTask::getOcrPageTaskStatus, OcrPageTaskStatusEnum.OCRTASKFINISHED.getCode())
                .update();
    }

    @Override
    public boolean saveFileImage(String taskSn, Integer pageNo,String imageUrl) {
        return lambdaUpdate().eq(OcrPageTask::getTaskSn, taskSn)
                .eq(OcrPageTask::getPageNo, pageNo)
                .set(OcrPageTask::getImageUrl,imageUrl)
                .set(OcrPageTask::getOcrPageTaskStatus, OcrPageTaskStatusEnum.QUEUING.getCode())
                .update();
    }

    @Override
    public Integer countFilesByTaskSnAndStatus(String taskSn, Integer status) {

        return count(new LambdaQueryWrapper<OcrPageTask>().eq(OcrPageTask::getTaskSn, taskSn)
                .eq(OcrPageTask::getOcrPageTaskStatus, status)
                .eq(OcrPageTask::getIsDel, 0));

    }

    @Override
    public int removeTasksByTaskSn(String taskSn) {

        return ocrPageTaskMapper.deleteByTaskSn(taskSn) ;

    }

    @Override
    public boolean unLockOcrPageTask(String ocrPageTaskSn) {

        OcrPageTask ocrPageTask = getById(ocrPageTaskSn);
        if(ocrPageTask.getOcrPageTaskStatus().equals(OcrPageTaskStatusEnum.OCRTASKFAILED.getCode())){
            return true;
        }
        Integer retryCount = ocrPageTask.getRetryCount();
        int status;
        if(retryCount > 1){
            status = OcrPageTaskStatusEnum.OCRTASKFAILED.getCode();
            fileTaskService.tasksFailed(ocrPageTask.getParentTaskSn(),"10003:ocr失败");
        }else{
            status = OcrPageTaskStatusEnum.QUEUING.getCode();
        }
        return lambdaUpdate().eq(OcrPageTask::getOcrPageTaskSn, ocrPageTaskSn)
                .set(OcrPageTask::getOcrPageTaskStatus, status)
                .set(OcrPageTask::getRetryCount, ocrPageTask.getRetryCount()+1)
                .update();

    }

    @Override
    public boolean tasksFailed(String parentTaskSn) {
        return lambdaUpdate().eq(OcrPageTask::getParentTaskSn,parentTaskSn)
                .set(OcrPageTask::getOcrPageTaskStatus, OcrPageTaskStatusEnum.OCRTASKFAILED.getCode()).update();
    }

    @Override
    public int deleteByParentTaskSn(String parentTaskSn) {
        return ocrPageTaskMapper.deleteByParentTaskSn(parentTaskSn);
    }

    @Override
    public int deleteByOcrPageTaskSn(String ocrPageTaskSn) {
        return ocrPageTaskMapper.deleteByOcrPageTaskSn(ocrPageTaskSn);
    }

    /**
     * 检查文件写携带场景并分割
     */
    @Override
    public void checkAndDivisionTemplate(String taskSn, String templateId) {
        // 检查场景内容
        FileTask fileTask = fileTaskService.getTaskByTaskSn(taskSn);
        String readFile = FileUtils.loadJson(FDFS_DOMAIN + fileTask.getJSONUrl());
        logger.info("fileTask:" + fileTask.getJSONUrl());
        String jsonStr = JSON.parse(readFile).toString();
        List<OcrResultBO> ocrResultBOList = JSONArray.parseArray(jsonStr, OcrResultBO.class);


        boolean isIncludeTables = false;
        // 含单个表格的hash结构<页号 + 表格ID, 表格所有行>
        LinkedHashMap<String, List<OcrResultBO.Line>> tablesHash = new LinkedHashMap<>();
        // 公共部分行
        List<OcrResultBO.Line> commonPart = new ArrayList<>();

        logger.info("**********遍历json信息 将数据分成表格数据以及公共数据**********");
        // 纯文本内容所有line
        List<List<OcrResultBO.Line>> allTextLines = new ArrayList<>();
        // 遍历json信息 将数据分成表格数据以及公共数据 todo 优化可抽出方法
        for (OcrResultBO ocrResultBO : ocrResultBOList) {
            // 纯文本内容单页的line
            List<OcrResultBO.Line> singlePageLines = new ArrayList<>();
            for (OcrResultBO.Paragraph paragraph : ocrResultBO.getParagraphs()) {
                for (OcrResultBO.Line line : paragraph.getLines()) {
                    if (TABLE_KEYWORDS.equals(line.getObjType()) && !"::".equals(line.getObjContent())) {
                        // 页号 + tableId 唯一确认一个表格
                        String tableKey = ocrResultBO.getPageNo() + "_" + line.getTableId();
                        // 文件中含有table
                        isIncludeTables = true;

                        // 组装table
                        if (tablesHash.get(tableKey) == null) {
                            // 不存在该表，则新建插入
                            List<OcrResultBO.Line> newTable = new ArrayList<>();
                            newTable.add(line);
                            tablesHash.put(tableKey, newTable);
                        } else {
                            // 存在该表格，则新增替换
                            List<OcrResultBO.Line> oldTable = tablesHash.get(tableKey);
                            oldTable.add(line);
                            tablesHash.replace(tableKey, oldTable);
                        }
                    } else if (!"::".equals(line.getObjContent())) {
                        // 不为表格则为公共部分
                        commonPart.add(line);
                        singlePageLines.add(line);
                    }
                }
            }
            allTextLines.add(singlePageLines);
        }
        // 判断走表格方法前，所有的表格是否都不含有场景信息 - 走纯文本 todo 抽出方法
        List<OcrResultBO.Line> firstTable = tablesHash.get("0_0");
        if (null != firstTable && firstTable.size() != 0) {
            // 判断第一段是否含有场景信息
            String firstTemplateSn = matchingTemplate(firstTable, true, templateId).get(0);
            boolean isContainTemplate = StringUtils.isNotEmpty(firstTemplateSn);
            if (!isContainTemplate) {
                commonPart.addAll(firstTable);
                tablesHash.remove("0_0");
            }
        }
        // todo 抽出独立表格、纯文本方法
        if (isIncludeTables && !tablesHash.keySet().isEmpty()) {
            logger.info("**********开始合并表格**********");
            HashMap<String, List<String>> tableIdAndTemplateIdsMap = new HashMap<>();
            // 合并表格 识别场景信息
            HashMap<String, String> tableIdAndTemplateIdMap = mergeTables(tablesHash, commonPart, templateId, tableIdAndTemplateIdsMap);
            // 循环所有表格-拼装数据格式 todo 抽出组合数据方法
            Set<String> singlePages = tablesHash.keySet();
            int pageNumber = 0;
            logger.info("singlePages" + JSON.toJSONString(singlePages));
            for (String singlePage : singlePages) {

                List<OcrResultBO.Line> lines;
                lines = tablesHash.get(singlePage);

                // 组合数据
                OcrResultBO singleResult = new OcrResultBO();
                List<OcrResultBO.Paragraph> paragraphs = new ArrayList<>();
                OcrResultBO.Paragraph paragraph = new OcrResultBO.Paragraph();

                if (commonPart.size() > 0 && MERGE_NON_STANDARD_TEMPLATE_ID.equals(templateId)) {
                    lines.addAll(0, commonPart);
                } else {
                    lines.addAll(commonPart);
                }
                // 构造后处理使用结构 写入json_url中
                paragraph.setLines(lines);
                paragraphs.add(paragraph);
                singleResult.setParagraphs(paragraphs);
                List<OcrResultBO> result = new ArrayList<>();
                result.add(singleResult);

                // 构造预测使用结构 写入ocr_json_url中
                OcrJsonBO ocrJsonBO = new OcrJsonBO();
                ocrJsonBO.setLineList(lines);
                List<OcrJsonBO> ocrJsonBOS = new ArrayList<>();
                ocrJsonBOS.add(ocrJsonBO);

                // 构造后处理使用结构 写入clean_text_url中
                StringBuilder cleanText = new StringBuilder();
                for (OcrResultBO.Line line : lines) {
                    String lineText = line.getObjContent();
                    if (StringUtils.isNotEmpty(lineText) && !"::".equals(lineText)) {
                        cleanText.append(lineText);
                    }
                }

                // 生成子任务
                if (Objects.equals(templateId, MERGE_NON_STANDARD_TEMPLATE_ID)) {
                    logger.info("**********TA申购场景ID：" + singlePage + "_" + tableIdAndTemplateIdsMap.get(singlePage));
                    for (String templateSn : tableIdAndTemplateIdsMap.get(singlePage)) {
                        logger.info("singlePage:" + singlePage);
                        createSonFileTask(result, ocrJsonBOS, cleanText.toString(), taskSn, templateSn, pageNumber);
                    }
                } else {
                    logger.info("**********开基确认书场景ID：" + singlePage + "_" + tableIdAndTemplateIdMap.get(singlePage));
                    createSonFileTask(result, ocrJsonBOS, cleanText.toString(), taskSn, tableIdAndTemplateIdMap.get(singlePage), pageNumber);
                }
                pageNumber++;
            }
        } else {
            int pageNumber = 0;
            logger.info("**********开始处理纯文本场景**********");
            // 纯文本只走单个场景 检查出所有场景信息，并给出最优先的
            for (List<OcrResultBO.Line> lines : allTextLines) {
                // TA申购场景 - 需要识别文本单页中多个场景
                List<String> templateSnsList = matchingTemplate(lines, false, templateId);
                logger.info("识别场景：" + JSON.toJSONString(templateSnsList));
                for (String templateSn : templateSnsList) {
                    AssertUtil.isEmpty(templateSn, "场景信息为空请检查 taskSn: " + taskSn);
                    logger.info("**********更新场景信息 templateSn:" + templateSn + " **********");
                    //fileTaskService.updateTaskTemplateIdById(taskSn, templateSn);

                    // 组合数据
                    OcrResultBO singleResult = new OcrResultBO();
                    List<OcrResultBO.Paragraph> paragraphs = new ArrayList<>();
                    OcrResultBO.Paragraph paragraph = new OcrResultBO.Paragraph();

                    // 构造后处理使用结构 写入json_url中
                    paragraph.setLines(lines);
                    paragraphs.add(paragraph);
                    singleResult.setParagraphs(paragraphs);
                    List<OcrResultBO> result = new ArrayList<>();
                    result.add(singleResult);

                    // 构造预测使用结构 写入ocr_json_url中
                    OcrJsonBO ocrJsonBO = new OcrJsonBO();
                    ocrJsonBO.setLineList(lines);
                    List<OcrJsonBO> ocrJsonBOS = new ArrayList<>();
                    ocrJsonBOS.add(ocrJsonBO);

                    // 构造后处理使用结构 写入clean_text_url中
                    StringBuilder cleanText = new StringBuilder();
                    for (OcrResultBO.Line line : lines) {
                        String lineText = line.getObjContent();
                        if (StringUtils.isNotEmpty(lineText) && !"::".equals(lineText)) {
                            cleanText.append(lineText);
                        }
                    }
                    createSonFileTask(result, ocrJsonBOS, cleanText.toString(), taskSn, templateSn, pageNumber);
                }
                pageNumber++;
            }

        }
    }

    /**
     * 生成子任务
     */
    public void createSonFileTask (List<OcrResultBO> result,List<OcrJsonBO> ocrJsonBOS,String cleanJsonText, String taskSn,String templateId, int pageNumber) {

        // 开基分红不做处理
        if ("FH".equals(templateId)) {
            logger.info("******************开基分红不做处理************************");
            return;
        }
        logger.info("********** 开始生成子任务 taskSn:" + taskSn + "**********");
        // 查询文件任务作为父任务，更新父任务ParentFileTaskSn为 0 - 后续抽取模型过滤父任务
        FileTask fatherFileTask = fileTaskService.getTaskByTaskSn(taskSn);
        fileTaskService.lambdaUpdate().eq(FileTask::getTaskSn, fatherFileTask.getTaskSn())
                .set(FileTask::getParentFileTaskSn, "0").update();

        // 写成文件
        logger.info("****************** 开始写入文件 ******************");

        String json= JSONArray.toJSONString(result);
        String ocrJson= JSONArray.toJSONString(ocrJsonBOS);


        // 写成单个文件传入fds
        String jsonUrl = FdfsUtils.uploadBytes(json);
        String ocrJsonUrl = FdfsUtils.uploadBytes(ocrJson);
        String cleanTextUrl = FdfsUtils.uploadBytes(cleanJsonText);

        // 生成table_json_url 并写入
        List<ParseTableVO> tables = new ArrayList<>();
        try{
            logger.info("****************** 开始进行表格处理 ******************");
            tables = TableUtil.handleTables(JSONArray.parseArray(ocrJson.trim()));
            logger.info("****************** 表格处理完成 ******************");
        }catch (Exception e) {
            logger.info("表格数据处理失败");
            e.printStackTrace();
        }

        String tableJsonUrl = FdfsUtils.uploadBytes(JSONArray.toJSONString(tables));


        // 写为单个file_task
        SnowflakeIdWorker snowflakeIdWorker = new SnowflakeIdWorker();
        FileTask sonFileTask = (FileTask) ObjectUtils.deepClone(fatherFileTask);

        assert sonFileTask != null;
        sonFileTask.setTaskSn(String.valueOf(snowflakeIdWorker.nextId()));
        sonFileTask.setParentTaskSn(String.valueOf(snowflakeIdWorker.nextId()));
        sonFileTask.setParentFileTaskSn(fatherFileTask.getTaskSn());
        sonFileTask.setTemplateId(templateId);
        sonFileTask.setFileStatus(FileStatusEnum.OCRTASKFINISHED.getCode());
        sonFileTask.setJSONUrl(jsonUrl);
        sonFileTask.setOcrJsonUrl(ocrJsonUrl);
        sonFileTask.setCleanTextUrl(cleanTextUrl);
        sonFileTask.setTableJsonUrl(tableJsonUrl);

        fileTaskService.save(sonFileTask);


        logger.info("****************** 子任务任务写入 ******************");
    }

    /**
     * 提取场景
     */
    public List<String> matchingTemplate (List<OcrResultBO.Line> lines, boolean isTableFile, String templateId) {
        logger.info("****************** 匹配场景信息 *****************" );
        List<String> templateKeys = new ArrayList<>();
        for (OcrResultBO.Line line : lines) {
            List<String> templateKeyList = getTemplateId(line, isTableFile, templateId);
            // 此处templateKeyList可能为空 使用增强for循环
            for (String templateKey : templateKeyList) {
                System.out.println(templateKey);
                if (!templateKeys.contains(templateKey)) {
                    templateKeys.add(templateKey);
                }
            }
        }
        String templateSn = priorityJudgment(templateKeys);
        if (Objects.equals(templateId, MERGE_TEMPLATE_ID)) {
            return Collections.singletonList(templateSn);
        } else {
            return templateKeys;
        }
    }


    /**
     * 获取每一行中的场景Id
     */
    public List<String> getTemplateId (OcrResultBO.Line line, boolean isTableFile, String templateId) {
        List<String> templateKeys = new ArrayList<>();
        boolean isLineContainsTemplate;
        // 可新增混合场景 对应不同的map可更换关键词
        HashMap<String, List<String>> map;
        boolean iskJTemplate = Objects.equals(templateId, MERGE_TEMPLATE_ID);
        if (iskJTemplate) {
            map = KJ_TEMPLATE_MAPS;
        } else {
            map = TA_SG_TEMPLATE_MAPS;
        }

        for (String templateKey : map.keySet()) {
            for (String templateMap : map.get(templateKey)) {
                if (isTableFile) {
                    if (line == null) {
                        logger.info("line" + line);
                        continue;
                    }
                    // 单个表格情况下需要判断文本 + 表格
                    if (null ==  line.getCells() && line.getObjContent().contains(templateMap)) {
                        templateKeys.add(templateKey);
                    } else if (null !=  line.getCells()){
                        for (OcrResultBO.Cell cell : line.getCells()) {
                            // 过滤cell为空无法判断场景信息的情况
                            if (cell == null) {
                                // logger.error("cell为空无法判断场景信息");
                                continue;
                            }
                            String cellContents = cell.getObjContent();
                            if (StringUtils.isNotEmpty(cellContents)) {
                                // 过滤词：关键词+“费”、关键词+“利”
                                if (iskJTemplate && (cellContents.contains(templateMap + "费") || cellContents.contains(templateMap + "利"))) {
                                    continue;
                                }
                                isLineContainsTemplate = cellContents.contains(templateMap);
                                if (isLineContainsTemplate) {
                                    templateKeys.add(templateKey);
                                }
                            }
                        }
                    }
                } else {
                    isLineContainsTemplate = line.getObjContent().contains(templateMap);
                    if (isLineContainsTemplate) {
                        templateKeys.add(templateKey);
                    }
                }
            }
        }
        return templateKeys;
    }

    /**
     * 优先级判断
     */
    public String priorityJudgment (List<String> templateKeys) {
        int priorityMax = 0;
        String result = "";
        for (String templateKey : templateKeys) {
            // 对场景进行优先级判断
            switch (templateKey) {
                //强增（减）
                case "NB2_983D871C":
                    if (3 > priorityMax) {
                        priorityMax = 3;
                        result = "NB2_983D871C";
                    }
                    // 转换
                case "NB2_2405CC0D":
                    if (3 > priorityMax) {
                        priorityMax = 3;
                        result = "NB2_2405CC0D";
                    }
                // 申购
                case "NB2_3E68FF7C":
                    if (2 > priorityMax) {
                        priorityMax = 2;
                        result = "NB2_3E68FF7C";
                    }
                    // 赎回
                case "NB2_C0AA242E":
                    if (2 > priorityMax) {
                        priorityMax = 2;
                        result = "NB2_C0AA242E";
                    }
                // 分红
                case "FH":
                    if (1 > priorityMax) {
                        priorityMax = 1;
                        result = "FH";
                    }
                default:
                    return templateKeys.get(0);
            }
        }

        return result;
    }

    /**
     * 合并表格
     * 第一个表格如果 不携带场景信息划入公共信息
     * 除了第一个表格以外，前后表格其中只有一个带有场景信息（遍历所有的line根据正则表达式进行匹配）则进行合并（根据line行数相加是否正常 进行判断向前合并还是向后合并）
     */
    public HashMap<String, String>  mergeTables (LinkedHashMap<String, List<OcrResultBO.Line>> tablesHash, List<OcrResultBO.Line> commonPart, String templateId, HashMap<String, List<String>> tableIdAndTemplateIdsMap) {

        HashMap<String, String> tableIdAndTemplateIdMap = new HashMap<>();
        List<OcrResultBO.Line> firstTable = tablesHash.get("0_0");
        if (null != firstTable && firstTable.size() != 0) {
            // 判断第一段是否含有场景信息
            String firstTemplateSn = matchingTemplate(firstTable, true, templateId).get(0);
            boolean isContainTemplate = StringUtils.isNotEmpty(firstTemplateSn);
            if (!isContainTemplate) {
                commonPart.addAll(firstTable);
                tablesHash.remove("0_0");
                logger.info("**************** 第一个表格不含场景信息，移除处理 *********************");
            }
        }
        // 获取所有tableKey，用于循环处理每个表格中的数据
        Set<String> tableKeys = tablesHash.keySet();
        // 计算合并表格时需要的数据
        List<OcrTableBO> ocrTableBOS = new ArrayList<>();
        List<Integer> lineNumList = new ArrayList<>();
        for (String tableKey : tableKeys) {
            List<OcrResultBO.Line> lines = tablesHash.get(tableKey);
            // 构造表格对象 todo 优化可将 tableHash 转换为 table对象
            OcrTableBO ocrTableBO = new OcrTableBO();
            ocrTableBO.setLines(lines);
            ocrTableBO.setTableNum(tableKey);

            int lineNumber = 0;
            for (OcrResultBO.Line line : lines) {
                // 记录line行数
                //logger.info("line:" + line.getObjContent());
                lineNumber ++;
            }
            // 文件为单表情况下，增加文本作为场景内容判断
            List<String> templateKeyList;
            if (tablesHash.keySet().size() == 1) {
                List<OcrResultBO.Line> textAndLines = new ArrayList<>();
                textAndLines.addAll(commonPart);
                textAndLines.addAll(lines);
                templateKeyList = matchingTemplate(textAndLines, true, templateId);
            } else {
                // 根据内容判断是否含有场景信息
                templateKeyList = matchingTemplate(lines, true, templateId);
            }
            logger.info("templateKeyList*************************:" + templateKeyList);
            if (StringUtils.isNotEmpty(templateKeyList.get(0))) {
                ocrTableBO.setTemplateId(templateKeyList.get(0));
                ocrTableBO.setIsContainsTemplate(true);
                // 返回每个表格的场景信息
                tableIdAndTemplateIdMap.put(tableKey, templateKeyList.get(0));
            }
            if (!MERGE_TEMPLATE_ID.equals(templateId)) {
                tableIdAndTemplateIdsMap.put(tableKey, templateKeyList);
            }

            lineNumList.add(lineNumber);
            ocrTableBO.setLineNum(lineNumber);
            ocrTableBOS.add(ocrTableBO);
        }
        // 待合并的表格id 例: 0_1,1_0
        List<String> mergeList = new ArrayList<>();
        // 计算众数
        List<Integer> mode = mode(lineNumList.stream().mapToInt(i->i).toArray());
        logger.info("【众数】" + mode);
        for (OcrTableBO ocrTableBO : ocrTableBOS) {
            if (ocrTableBO.getLineNum() != mode.get(0)) {
                mergeList.add(ocrTableBO.getTableNum());
            }
        }
        // TA申赎场景不进行合并
        if (MERGE_TEMPLATE_ID.equals(templateId)) {
            // 每2页进行合并
            for (int i = 0; i < mergeList.size(); i += 2) {
                //logger.info("******************ocrTableBOS：" + JSONArray.toJSONString(ocrTableBOS));
                // 判断场景信息位置
                boolean isFrontTableContainsTemplate = tableIdAndTemplateIdMap.get(mergeList.get(i)) != null;
                if (mergeList.size() % 2 != 0 ) {
                    logger.error("表格检查异常,待合并表格数量必须为偶数：" + mergeList.size());
                    continue;
                }
                if (tableIdAndTemplateIdMap.get(mergeList.get(i)) == tableIdAndTemplateIdMap.get(mergeList.get(i + 1))) {
                    logger.error("表格检查异常,前后表格为同一场景：" + mergeList.size());
                    continue;
                }
                // 后一个表格必须为页首例如 1_0 2_0 3_0 4_0
                if(!Objects.equals((mergeList.get(i + 1).split("_"))[1], "0")) {
                    logger.error("合并异常,不符合后一个表格位置在页首，页号为：" + mergeList.get(i) +"___" + mergeList.get(i + 1));
                    continue;
                }
                // 根据场景类型所在的tableId 记录新的表
                List<OcrResultBO.Line> frontTable = tablesHash.get(mergeList.get(i));
                List<OcrResultBO.Line> afterTable = tablesHash.get(mergeList.get(i + 1));

                frontTable.addAll(afterTable);
                if (isFrontTableContainsTemplate) {
                    tablesHash.replace(mergeList.get(i), frontTable);
                    tablesHash.remove(mergeList.get(i + 1));
                } else {
                    tablesHash.replace(mergeList.get(i + 1), frontTable);
                    tablesHash.remove(mergeList.get(i));
                }
            }
        }

        return tableIdAndTemplateIdMap;
    }

    /**
     * 计算众数
     */
    public static List<Integer> mode(int[] arr) {
        Map<Integer, Integer> map = new HashMap<>();
        Set<Map.Entry<Integer, Integer>> set = map.entrySet();
        List<Integer> list = new ArrayList<>();
        // 结果
        List<Integer> res = new ArrayList<>();
        // 统计元素出现的次数，存入Map集合
        for (int item : arr) {
            map.put(item, map.getOrDefault(item, 0) + 1);
        }
        // 将出现的次数存入List集合
        map.forEach((k, v) -> {
            list.add(v);
        });
        //集合排序
        Collections.sort(list);
        // 得到最大值
        int max = list.get(list.size() - 1);
        // 根据最大值获取众数
        for (Map.Entry<Integer, Integer> entry : set) {
            if (entry.getValue() == max) {
                res.add(entry.getKey());
            }
        }
        return res;
    }

}
